#include "stdafx.h"
#include "CScrLanguage.h"
#include "CConsole.h"
#include "CParser.h"
#include "CCompiler.h"
#include "CScripts.h"

//CLListEx<CScriptHeader::CSegment*>		CScriptHeader::s_Segments;

void CScript::Parse()
{
	CSubScript::Parse();
	/*for(auto pLink = m_SubScripts.Begin(); pLink != m_SubScripts.Bad(); ++pLink)
	{
		if(pLink->IsUsed())
		{
			pLink->Parse();
		}
	}*/
}
void CScript::Compile()
{
	CSubScript::Compile();

	/*for(auto pLink = m_SubScripts.Begin(); pLink != m_SubScripts.Bad(); ++pLink)
	{
		if(pLink->IsUsed())
		{
			pLink->Compile();
		}
	}*/
}
void CScript::Build()
{
	if(!HasBuildFailed()) Parse();
	if(!HasBuildFailed()) Compile();
	if(!HasBuildFailed()) Copy();

	// BuildMainHeader();
	/*for(auto pLink = m_SubScripts.Begin(); pLink != m_SubScripts.Bad(); ++pLink)
	{
		if(pLink->GetParseInfo()->
	}*/
}
void CScript::Merge()
{
	char filepath[MAX_PATH];
	const char * pName = GetFile()->GetName();
	strcpy(filepath, GetDirectoryPath());
	strncat(filepath, pName, strrchr(pName, '.') - pName);
	strcat(filepath, ".scm");
	
	if(auto pFile = new CFile(filepath, FILE_MODE_WRITE, FILE_TYPE_BINARY))
	{
		// Get merging all the files
		if(auto pBuffer = new BYTE[SCRambl.Config.Performance.GetInputBufferSize()])
		{
			if(GetParseInfo() && GetParseInfo()->GetSize() && *m_szOutPath)
			{
				if(auto pSrcFile = new CFile(m_szOutPath, FILE_MODE_READ, FILE_TYPE_BINARY))
				{
					if(pSrcFile->OK())
					{
						while(!pSrcFile->HasEnded())
							pFile->Write(pBuffer, pSrcFile->Read(pBuffer, SCRambl.Config.Performance.GetInputBufferSize()));
						Print(SCRambl.BuildText(SCRambl.GetText("SCRIPTS", "M100"), NULL, 0, "%s%s", pSrcFile->GetName(), filepath));
					}
					delete pSrcFile;
				}
			}

			for(auto pLink = m_SubScripts.Begin(); pLink != m_SubScripts.Bad(); ++pLink)
			{
				if(!pLink->IsUsed() || !pLink->GetParseInfo() || !pLink->GetParseInfo()->GetSize() || !*pLink->m_szOutPath)
					continue;
			
				if(auto pSrcFile = new CFile(pLink->m_szOutPath, FILE_MODE_READ, FILE_TYPE_BINARY))
				{
					if(pSrcFile->OK())
					{
						while(!pSrcFile->HasEnded())
							pFile->Write(pBuffer, pSrcFile->Read(pBuffer, SCRambl.Config.Performance.GetInputBufferSize()));
						Print(SCRambl.BuildText(SCRambl.GetText("SCRIPTS", "M100"), NULL, 0, "%s%s", pSrcFile->GetName(), filepath));
					}
					delete pSrcFile;
				}
			}

			delete pBuffer;
		}

		delete pFile;
	}
	else throw(SCRambl.BuildText(SCRambl.GetText("SCRIPTS", "M101"), NULL, 0, "%s", filepath));
}
void CScript::Copy()
{
	auto pStr = strrchr(m_szOutPath, '\\');
	if(!pStr) pStr = m_szOutPath;
	char szDestPath[MAX_PATH];
	strcpy(szDestPath, GetDirectoryPath());
	strcat(szDestPath, pStr);
	if(FileCopy(m_szOutPath, szDestPath))
		Print(SCRambl.BuildText(SCRambl.GetText("SCRIPTS", "M106"), NULL, 0, "%s", szDestPath));
	else
		Print(SCRambl.BuildText(SCRambl.GetText("SCRIPTS", "M107"), NULL, 0, "%s%s", szDestPath, m_szOutPath));
}

void CSubScript::Parse()
{
	if(IsMergedWith())
	{
		// This is the main script which is compiled first, so initialise the compile size
		GetScript()->SetCompileSize(0);
	}

	// Do we even need to reparse this?
	if(IsParseNeeded())
	{
		Init();
		auto nParsedBytes = Parser.Parse(this);
		if(nParsedBytes != -1)
		{
			auto time = GetTime();
			LastParseTime(time);
			GetParseInfo()->SetSize(nParsedBytes);
			GetParseInfo()->SetTimeParsed(time);
			GetParseInfo()->ClearTokenList();
	
			// Add all tokens to the parse info - a linked list of tokens
			for(int x=0; x < Parser.s_nNumLinesParsed; ++x)
			{
				for(int y=0; y < MAX_TOKENS; ++y)
				{
					auto pToken = Parser.GetTokenReadOnly(x, y);
					if(!pToken || pToken->GetType() == TOKEN_NONE) break;
					GetParseInfo()->AddToken(pToken);
				}
			}
		}
		else
		{
			// We parsed -1 bytes... no? Then there was an error
			FailBuild();
		}
	}
	else Print(SCRambl.BuildText(SCRambl.GetText("SCRIPTS", "M102"), NULL, 0, "%s", GetFile()->GetName()));
}
void CSubScript::Compile()
{
	if(GetParseInfo() ? GetParseInfo()->GetSize() : false)
	{
		// If the original file is there we don't need to recompile
		if(*m_szOutPath)
		{
			auto pFile = new CFile(m_szOutPath, FILE_MODE_READ);
			if(pFile->OK())
			{
				delete pFile;
				Print(SCRambl.BuildText(SCRambl.GetText("SCRIPTS", "M103"), NULL, 0, "%s", GetFile()->GetName()));
				return;
			}
		}
		
		// Name the file either depending on the filename or a noname index (change this to use SCRIPT_NAME?)
		static int s_nNumNamelessScripts = 0;
		char filename[MAX_PATH];
		
		if(GetFile()->GetName()) szncpy(filename, GetFile()->GetName(), sizeof(filename)-4);
		else sprintf(filename, "noname%d", ++s_nNumNamelessScripts);
	
		strtok(filename, ".");
		strcat(filename, ".scc");
		
		// Try to create the output file
		auto pFile = new CFile(filename, FILE_MODE_WRITE);
		if(pFile && pFile->OK())
		{
			// Initiate the compiler with the token list
			Compiler.Init(GetParseInfo()->GetFirstToken(), GetParseInfo()->GetLastToken());
		
			// Set the begin offset to the current number of bytes compiled for merges
			if(GetScript()->IsMergedWith())
				GetParseInfo()->SetBeginOffset(GetScript()->GetCompileSize());

			// Compile and print results
			int nBytes = Compiler.Compile(this, pFile);
			Print(SCRambl.BuildText(SCRambl.GetText("SCRIPTS", "M105"), NULL, 0, "%s%d", GetFile()->GetName(), nBytes));
		
			// Increase the number of bytes compiled, so that 7 lines earlier we can use it
			GetScript()->SetCompileSize(GetScript()->GetCompileSize() + nBytes);
		}
		else
		{
			// Couldn't create file
			Error(SCRambl.BuildText(SCRambl.GetText("SCRIPTS", "M104"), NULL, 0, "%s", filename));
		}
	}
	else Print(SCRambl.GetText("SCRIPTS", "M108"));
}
void CSubScript::Build()
{
	Parse();
	Compile();
	Copy();
}
void CSubScript::Copy()
{
	this;
}

void CScript::OnParse()
{

}

void CScriptParseInfo::OnParse()
{
}

void CSubScript::Add(CToken * pToken)
{
	if(pToken->IsSkippedDuringCompilation()) return;
	switch(pToken->GetType())
	{
	case TOKEN_NONE:
	case TOKEN_UNKNOWN:
		return;
	default:
		GetParseInfo()->AddToken(pToken);
	}
}
void CSubScript::Add(CLabel * pLabel)
{
	if(m_pScript->FindLabel(pLabel->GetName(), pLabel->GetHash()))
		throw(SCRambl.BuildText(SCRambl.GetText("ERRORS", "E1106"), NULL, 0, "%s%d", pLabel->GetName(), pLabel->GetLineNum()));

	m_pScript->Add(pLabel);
	m_Labels.Add(pLabel->GetHash(), pLabel);
	m_LabelList.Add(pLabel);
}
void CSubScript::Add(CVariable * pVariable)
{
	if(pVariable->IsLocal())
	{
		if(!m_pScope)
			throw(SCRambl.BuildText(SCRambl.GetText("ERRORS", "E1134"), NULL, 0, "%s", pVariable->GetType()->GetName()));
		if(m_pScope->GetNumVariables() > CLimits::GetMaxScriptVar())
			throw(SCRambl.BuildText(SCRambl.GetText("ERRORS", "E1135"), NULL, 0, "%d%s", CLimits::GetMaxScriptVar(), pVariable->GetType()->GetName()));
		if(m_pScope->GetVariable(pVariable->GetName()))
			throw(SCRambl.BuildText(SCRambl.GetText("ERRORS", "E1136"), NULL, 0, "%s", pVariable->GetType()->GetName()));
			
		m_pScope->Add(pVariable);
	}
	else if(m_pScript)
		m_pScript->Add(pVariable);
}

void CSubScript::OnParse()
{
}

void CSubScript::OnParseEnd()
{
}

/*bool CScriptHeader::LoadFromFile(const char *szFilePath)
{
	if(auto pFile = new CFile(szFilePath, FILE_MODE_READ, FILE_TYPE_TEXT))
	{
		CSegment * pSegment = nullptr;

		while(pFile->ReadLine(g_szTempString, sizeof(g_szTempString)))
		{
			char * pStr = g_szTempString;
			pStr = ltrim(pStr);
			switch(*g_szTempString)
			{
			case ';':
			case '\n':
			case '\r':
			case '\0':
				continue;
			case '[':
				if(pStr = strtok(++pStr, "]"))
				{
					if(!strnicmp(pStr, "SEG", 3))
					{
						pStr += sizeof("SEG");
						pSegment = new CSegment(StringToInt(pStr));
						s_Segments.Add(pSegment);
					}
				}
				break;
			default:
				{
					CHeaderData * pData = new CHeaderData();

					if(pStr = strtok(pStr, " \t\r\n"))
					{
						int nSize = 0;
						if(!stricmp(pStr, "BYTE")) nSize = 1;
						else if(!stricmp(pStr, "WORD")) nSize = 2;
						else if(!stricmp(pStr, "DWORD")) nSize = 4;
						if(pData->m_nSize = nSize)
						{
							pData->m_eCmd = HEADER_CMD_MOV;
							pStr = strtok(nullptr, " \t\r\n");
							
							if(IsStringInt(pStr))
							{
								pData->m_nVal = StringToInt(pStr);
							}
							else
							{
								if(!stricmp(pStr, "SEGID")) pData->m_eVar = HEADER_VAR_SEGID;
								else if(!stricmp(pStr, "TARGETGAME")) pData->m_eVar = HEADER_VAR_TARGETGAME;
								else if(!stricmp(pStr, "MAINSIZE")) pData->m_eVar = HEADER_VAR_MAINSIZE;
								else if(!stricmp(pStr, "LARGESTMISSIONSIZE")) pData->m_eVar = HEADER_VAR_LARGESTMISSIONSIZE;
								else if(!stricmp(pStr, "NUMMISSIONS")) pData->m_eVar = HEADER_VAR_NUMMISSIONS;
								else if(!stricmp(pStr, "NUMMODELS")) pData->m_eVar = HEADER_VAR_NUMMODELS;
								else if(!stricmp(pStr, "HIGHESTNUMLVARS")) pData->m_eVar = HEADER_VAR_HIGHESTNUMLVARS;
								else if(!stricmp(pStr, "LARGESTSTREAMEDSCRIPTSIZE")) pData->m_eVar = HEADER_VAR_LARGESTSTREAMEDSCRIPTSIZE;
								else if(!stricmp(pStr, "LARGESTSTREAMEDSCRIPTSIZE")) pData->m_eVar = HEADER_VAR_NUMSTREAMEDSCRIPTS;
								else if(!stricmp(pStr, "VARSPACESIZE")) pData->m_eVar = HEADER_VAR_VARSPACESIZE;
								else pData->m_eVar = HEADER_VAR_NONE;
							}
						}
						else
						{
							if(!stricmp(pStr, "VAR_SPACE")) pData->m_eCmd = HEADER_CMD_VAR_SPACE;
							else if(!stricmp(pStr, "MODEL_NAMES")) pData->m_eCmd = HEADER_CMD_MODEL_NAMES;
							else if(!stricmp(pStr, "MISSION_OFFSETS")) pData->m_eCmd = HEADER_CMD_MISSION_OFFSETS;
							else if(!stricmp(pStr, "STREAMED_SCRIPT_INFO")) pData->m_eCmd = HEADER_CMD_STREAMED_SCRIPT_INFO;
							
							if(pStr = strtok(nullptr, " \t\r\n"))
							{
								if(IsStringInt(pStr)) pData->m_nVal = StringToInt(pStr);
							}
						}
					}

					pSegment->m_Data.Add(pData);
				}
			}
		}
		delete pFile;
		return true;
	}
	else throw("Failed to open file");
	return false;
}*/